package com.noodles.mall.config;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.noodles.mall.entity.User;
import com.noodles.mall.filter.CustomFilterInvocationSecurityMetadataSource;
import com.noodles.mall.filter.CustomUrlDecisionManager;
import com.noodles.mall.filter.LoginFilter;
import com.noodles.mall.service.UserService;
import com.noodles.mall.vo.UserVo;
import com.noodles.mall.web.JsonResult;
import com.noodles.mall.web.State;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;
import org.springframework.security.authentication.*;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.session.SessionRegistry;
import org.springframework.security.core.session.SessionRegistryImpl;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.session.ConcurrentSessionFilter;

import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserService userService;

    @Autowired
    CustomFilterInvocationSecurityMetadataSource customFilterInvocationSecurityMetadataSource;

    @Autowired
    CustomUrlDecisionManager customUrlDecisionManager;

    /**
     *
     * @return
     * @throws Exception
     */
    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        System.out.println("WebSecurityConfig: authenticationManagerBean");
        return super.authenticationManagerBean();
    }

    @Bean
    public SessionRegistry sessionRegistry() {
        System.out.println("WebSecurityConfig: sessionRegistry");
        return new SessionRegistryImpl();
    }

    @Bean
    public LoginFilter loginFilter() throws Exception {
        System.out.println("WebSecurityConfig: loginFilter 开始");
        LoginFilter loginFilter = new LoginFilter();
        loginFilter.setAuthenticationSuccessHandler((request, response, authentication) -> {
            response.setContentType("application/json;charset=utf-8");
            PrintWriter out = response.getWriter();
            User user = (User) authentication.getPrincipal();
            UserVo userVo = new UserVo();
            BeanUtils.copyProperties(user, userVo);
            out.write(new ObjectMapper().writeValueAsString(JsonResult.success(userVo)));
            out.flush();
            out.close();
        });
        loginFilter.setAuthenticationFailureHandler((request, response, exception) -> {
                    response.setContentType("application/json;charset=utf-8");
                    PrintWriter out = response.getWriter();
                    JsonResult JsonResult =new JsonResult(State.ERR_LOGIN,null,null);
                    if (exception instanceof LockedException) {
                        JsonResult.setMsg("账户被锁定，请联系管理员!");
                    } else if (exception instanceof CredentialsExpiredException) {
                        JsonResult.setMsg("密码过期，请联系管理员!");
                    } else if (exception instanceof AccountExpiredException) {
                        JsonResult.setMsg("账户过期，请联系管理员!");
                    } else if (exception instanceof DisabledException) {
                        JsonResult.setMsg("账户被禁用，请联系管理员!");
                    } else if (exception instanceof BadCredentialsException) {
                        JsonResult.setMsg("用户名或者密码输入错误，请联系管理员!");
                    }
                    out.write(new ObjectMapper().writeValueAsString(JsonResult));
                    out.flush();
                    out.close();
                }
        );
        loginFilter.setAuthenticationManager(authenticationManagerBean());
        loginFilter.setFilterProcessesUrl("/login");
        System.out.println("WebSecurityConfig: loginFilter 结束");
        return loginFilter;
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        System.out.println("WebSecurityConfig: configure 开始");
        System.out.println("http: "+http);
        http.cors().and().csrf().disable().authorizeRequests().withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
            @Override
            public <O extends FilterSecurityInterceptor> O postProcess(O object) {
                object.setAccessDecisionManager(customUrlDecisionManager);
                object.setSecurityMetadataSource(customFilterInvocationSecurityMetadataSource);
                return object;
            }
        })
                .and()
                .logout().logoutUrl("/logout").deleteCookies("ACCESS_TOKEN", "JSESSIONID")
                .logoutSuccessHandler((request, response, authentication) -> {
                            response.setContentType("application/json;charset=utf-8");
                            PrintWriter out = response.getWriter();
                            out.write(new ObjectMapper().writeValueAsString(JsonResult.success("注销成功!")));
                            out.flush();
                            out.close();
                        }
                )
                .permitAll()
                .and()
                .exceptionHandling()
                // 未认证时的处理
                .authenticationEntryPoint((request, response, authException) -> {
                            response.setContentType("application/json;charset=utf-8");
                            PrintWriter out = response.getWriter();
                            JsonResult jr = null;
                            if (authException instanceof InsufficientAuthenticationException) {
                                jr = JsonResult.error(State.ERR_PERMISSION, "你没有权限访问");
                            }
                            out.write(new ObjectMapper().writeValueAsString(jr));
                            out.flush();
                            out.close();
                        }
                )
                .and()
                .addFilterAt(new ConcurrentSessionFilter(sessionRegistry(), event -> {
                    HttpServletResponse response = event.getResponse();
                    response.setContentType("application/json;charset=utf-8");
                    response.setStatus(HttpStatus.UNAUTHORIZED.value());
                    PrintWriter out = response.getWriter();
                    out.write(new ObjectMapper().writeValueAsString(
                            JsonResult.error(State.ERR_EXIST, "您已在另一台设备登录，本次登录已下线!")
                    ));
                    out.flush();
                    out.close();
                }), ConcurrentSessionFilter.class);
        http.addFilterAt(loginFilter(), UsernamePasswordAuthenticationFilter.class);
        System.out.println("WebSecurityConfig: configure 结束");
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        System.out.println("WebSecurityConfig: configure 开始");
        System.out.println("auth: "+auth);
        auth.userDetailsService(userService).passwordEncoder(passwordEncoder());
        System.out.println("WebSecurityConfig: configure 结束");
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        System.out.println("WebSecurityConfig: passwordEncoder");
        return new BCryptPasswordEncoder();
    }
}